package com.ebs.md.fxmonitor.marketmonitor;

/**
 * Perforce Revision:
 *  $File: //depot/MarketData/fxmonitor/main/src/main/java/com/ebs/md/fxmonitor/marketmonitor/CentralBankActivityListener.java $
 *  $Revision: #16 $  $Change: 115045 $  $DateTime: 2012/09/19 14:11:37 $  $Author: Arik.Sher $
 */

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.jms.JMSException;
import javax.jms.Message;

import com.ebs.commons.comp.IParsedPayload;
import com.ebs.market.monitor.MarketMonitor.CentralBankDealEvent;
import com.ebs.market.monitor.MarketMonitor.CentralBankQuoteEvent;
import com.ebs.market.monitor.MarketMonitor.DealSide;
import com.ebs.market.monitor.MarketMonitor.EventType;
import com.ebs.market.monitor.MarketMonitor.MarketIdentfier;
import com.ebs.market.monitor.MarketMonitor.QuoteSide;
import com.ebs.marketdata.dictionary.MatchingType;


public class CentralBankActivityListener
extends AbstractMonitorListenerAndPublisher
{
	final static int    MAIN_COMPONENT_VALUE  = 0;
	
    final static String MQ_MSG_NAME_NEW_QUOTE = "RQTM"; // MKTQTEAVAILABLE
    final static String MQ_MSG_NAME_DEAL      = "RDST"; // DEALSTATUSTAKER (looking at Local Takers(NA_DST) and InterRegion Takers(AA_DST)
    final static List<String> MQ_MSG_NAMES_QUOTE_ENDED;
    final static String PAYLOAD_NAME_TOTAL_AMOUNT = "qte_amt_result";
    final static String PAYLOAD_NAME_AVAIL_AMOUNT = "qte_amt_avail_result";
    
    static {
    	MQ_MSG_NAMES_QUOTE_ENDED = new ArrayList<String>(2);
    	MQ_MSG_NAMES_QUOTE_ENDED.add("RMQI"); // MKTQTEINTERRUPTED
    	MQ_MSG_NAMES_QUOTE_ENDED.add("RQTN"); // MKTQTEDONE
    }
    
    // this set contains quote_id's to be monitored, for later alerting Deals outgoing from those quotes.
    protected Set<String> activeMonitoredQuotes;
    
    
	public CentralBankActivityListener() {
		super();
		this.activeMonitoredQuotes = new HashSet<String>(128);
	}

	public void init() {
		super.init();
//		Assert.notNull(scheduledExecutorService, "'scheduledExecutorService' cannot be null");
	}
	
	public void destroyMe() {
	}

	/**
	 * 
	 * Searching for payload with 'RDST' or 'RQTM'
	 */
	@Override
	protected void handleParsedPayload(IParsedPayload payload) {
		String mqMsgType = payload.getParsedStringField("mq_msg_type");
		
		if        ( MQ_MSG_NAME_NEW_QUOTE.   equals  (mqMsgType) ) {
			handleIncomingQuote(payload);
		} else if ( MQ_MSG_NAMES_QUOTE_ENDED.contains(mqMsgType) ) {
			handleQuoteRemoval(payload);
		} else if ( MQ_MSG_NAME_DEAL.        equals(  mqMsgType) ) {
			handleIncomingDeal(payload);
		} else {
			// incoming message type is irrelevant. skipping it.
		}
	}

	protected void handleIncomingQuote(IParsedPayload payload) {
		String dealCode = extractDealCodeFromQuote(payload);
		
		if ( isBankActivityMonitored(dealCode) ) {
			if ( thresholdExceeds(payload) ) {
				
				activeMonitoredQuotes.add(extractQuoteIdFromQuote(payload));
				
				doPublish(payload,
					new CentralBankQuoteEventJmsMessageCreator(payload, getThresholdAmount(payload))
				);
				
			}
		}
	}

	protected void handleIncomingDeal(IParsedPayload payload) {
		String makerQuoteId = extractMakerQuoteIdFromDeal(payload);
		String takerQuoteId = extractTakerQuoteIdFromDeal(payload);
		
		boolean makerQuoteMonitored = activeMonitoredQuotes.contains(makerQuoteId);
		boolean takerQuoteMonitored = activeMonitoredQuotes.contains(takerQuoteId);
		boolean anyQuoteMonitored = (makerQuoteMonitored || takerQuoteMonitored);
		
		if ( anyQuoteMonitored && isMainComponent(payload) )
		{
			String baseQuoteId;
			String centralBankDealCode;
			if ( makerQuoteMonitored ) {
				baseQuoteId         =        makerQuoteId;
				centralBankDealCode = extractMakerDealCodeFromDeal(payload);
			} else {
				baseQuoteId         =        takerQuoteId;
				centralBankDealCode = extractTakerDealCodeFromDeal(payload);
			}
			
			doPublish(payload 
				,new CentralBankDealEventJmsMessageCreator(payload, centralBankDealCode, baseQuoteId)
			);
		}
	}
	
	
	/**
	 * 
	 * on purpose: not checking if ActivityMonitored, because configuration of actively monitored
	 * CentralBanks might change, and could leave zombie quotes in the Collection.
	 * 
	 * @param payload
	 */
	protected void handleQuoteRemoval(IParsedPayload payload) {
		activeMonitoredQuotes.remove(extractQuoteIdFromQuote(payload));
	}

	/**
	 * component_number = 0, means its the main Deal Message.
	 * when component_number > 0, it means mean nearer legs messages(for SAME deal)
	 * 
	 * using this to filter out the other components, so for example will not fire 3 events
	 * when deal is on Basket Ruble(which has 2 more components), but only one.
	 * 
	 * @param payload
	 * @return
	 */
	protected static boolean isMainComponent(IParsedPayload payload) {
		int componentNum = payload.getParsedIntField ("component_number", 999);
		
		return (componentNum == MAIN_COMPONENT_VALUE);
	}
	
	/**
	 * assuming that payload is Deal one(DST)
	 * @param payload
	 * @return
	 * 
	 * @see mq_msg_build.xml
	 */
	protected static String extractTakerQuoteIdFromDeal(IParsedPayload payload) {
		return payload.getParsedStringField ("taker_trans_id");
	}
	protected static String extractMakerQuoteIdFromDeal(IParsedPayload payload) {
		return payload.getParsedStringField ("maker_trans_id");
	}
	
	protected static String extractTakerDealCodeFromDeal(IParsedPayload payload) {
		return payload.getParsedStringField ("taker_floor_code");
	}
	protected static String extractMakerDealCodeFromDeal(IParsedPayload payload) {
		return payload.getParsedStringField ("maker_floor_code");
	}
	
	/**
	 * assuming that payload is Quote one
	 * @param payload
	 * @return
	 */
	protected static String extractQuoteIdFromQuote(IParsedPayload payload) {
		return payload.getParsedStringField ("ebs_trans_id");
	}
	
	/**
	 * assuming that payload is Quote one
	 * @param payload
	 * @return
	 */
	protected static String extractDealCodeFromQuote(IParsedPayload payload) {
		return payload.getParsedStringField ("qte_floor_code");
	}
	
	/**
	 * assuming that payload is Quote one
	 * @param payload
	 * @return total amount("qte_amt_result") and not the available("qte_amt_avail_result").
	 *         because not likely that CentralBank won't make a Hit, most likely it would MAKE a BID or an OFFER, 
	 *         and other's would hit their quote.
	 */
	protected static long extractAmountFromQuote(IParsedPayload payload) {
		return payload.getParsedLongField (PAYLOAD_NAME_TOTAL_AMOUNT);
	}
		
	@Override
	protected boolean thresholdExceeds(IParsedPayload payload) {
		// solve PTR 14838 - discarding FIX and CM. (similar to PTR 14738)
		final int matchingTypeId = payload.getParsedIntField(   "matching_type");
		final MatchingType matchingType = MatchingType.getById(matchingTypeId);
		if (    false
			 || MatchingType.Fix.equals(matchingType)
			 || MatchingType.CM .equals(matchingType) 
		) {
			return false;
		}
		
		long thresholdValue = getThresholdAmount(extractDealCodeFromQuote(payload));
		long actualAmount   = extractAmountFromQuote(payload);
		
		return ( actualAmount >= thresholdValue);
	}

	protected boolean isBankActivityMonitored(String dealCode) {
		if ( eventConfigProvider != null ) {
			return eventConfigProvider.isCentralBankActivityMonitored(dealCode);
		}
		return false;
	}
	
	protected EventMessageCreator getCreator(IParsedPayload payload) {
		throw new UnsupportedOperationException("should not call this method");
	}

	protected long getThresholdAmount(IParsedPayload payload) {
		return getThresholdAmount(extractDealCodeFromQuote(payload));
	}
	
	protected long getThresholdAmount(String dealCode) {
		if ( eventConfigProvider != null ) {
			return eventConfigProvider.getCentralBankActivityAmountThreshold(dealCode);
		}
		return EVENT_CENTRAL_BANK_DEFAULT_QUOTE_AMOUNT_THRESHOLD;
	}
	
	
	static class CentralBankQuoteEventJmsMessageCreator 
	extends AbstractEventMessageCreator<CentralBankQuoteEvent>
	{
		protected final long  amountThreshold;
		
		public CentralBankQuoteEventJmsMessageCreator(IParsedPayload payload, long amountThreshold) {
			super(payload);
			this.amountThreshold = amountThreshold;
		}

		public EventType getEventType() {
			return EventType.CENTRAL_BANK_QUOTE;
		}

		/**
		 * @see mq_msg_build.xml#RQTM
		 * RQTM = MarketQuoteAvailable
		 */
		public CentralBankQuoteEvent createEventObject(IParsedPayload payload) {
			CentralBankQuoteEvent.Builder builder = CentralBankQuoteEvent.newBuilder();
		
			builder.setDealCode(payload.getParsedStringField ("qte_floor_code"));
			
			MarketIdentfier.Builder miBuilder = MarketIdentfier.newBuilder();
			miBuilder. setBaseCurrencyIso(payload.getParsedStringField( "base_ccy"));
			miBuilder.setLocalCurrencyIso(payload.getParsedStringField("local_ccy"));
			miBuilder.setMatchingType(    payload.getParsedIntField(   "matching_type"));
			
			builder.setMarketId(miBuilder.build());
			builder.setEventTime(eventTimeMillis);
			
			final String quoteId = payload.getParsedStringField  ("ebs_trans_id");
			builder.setQuoteId(quoteId);
			final long actTime   = payload.getParsedLongField    ("msg_timestamp_long");
			builder.setQuoteTime(actTime);
			QuoteSide quoteSide = CommonTools.getQuoteSide(
								 payload.getParsedIntField   ("qte_type"));
			builder.setQuoteSide(quoteSide);
			builder.setQuoteRate(payload.getParsedStringField("prc_qte_value"));
//			builder.setQuoteAmount(payload.getParsedLongField(PAYLOAD_NAME_AVAIL_AMOUNT));
			builder.setQuoteAmount(payload.getParsedLongField(PAYLOAD_NAME_TOTAL_AMOUNT));
			
			String traderId = payload.getParsedStringField("trader_id", null);
			if ( traderId != null ) {
				builder.setTraderId(traderId);
			}
			String eventId = genEventId(quoteId, actTime);
			builder.setEventId(eventId);

			builder.setAmountThreshold(amountThreshold);
			
			CentralBankQuoteEvent built = builder.build();
			return built;
		}

		@Override
		protected void doAppendExtraProperties(Message jmsMessage) throws JMSException {
			jmsMessage.setStringProperty(JMS_MSG_PROPERTY_BASE_CURRENCY_ISO
					,payload.getParsedStringField( "base_ccy"));
			jmsMessage.setStringProperty(JMS_MSG_PROPERTY_LOCAL_CURRENCY_ISO
					,payload.getParsedStringField("local_ccy"));
		}

	}

	static class CentralBankDealEventJmsMessageCreator 
	extends AbstractEventMessageCreator<CentralBankDealEvent>
	{
		protected final long   eventTimeMillis;
		protected final String baseQuoteId;
		protected final String centralBankDealCode;
		
		public CentralBankDealEventJmsMessageCreator(IParsedPayload payload, String centralBankDealCode, String baseQuoteId) {
			super(payload);
			this.eventTimeMillis     = System.currentTimeMillis();
			this.centralBankDealCode = centralBankDealCode;
			this.baseQuoteId         = baseQuoteId;
		}

		public EventType getEventType() {
			return EventType.CENTRAL_BANK_DEAL;
		}

		/**
		 * @see mq_msg_build.xml#RDST
		 * DST = DealStatusTaker
		 */
		public CentralBankDealEvent createEventObject(IParsedPayload payload) {
			CentralBankDealEvent.Builder builder = CentralBankDealEvent.newBuilder();
		
			builder.setDealCode(centralBankDealCode);
			
			MarketIdentfier.Builder miBuilder = MarketIdentfier.newBuilder();
			miBuilder. setBaseCurrencyIso(payload.getParsedStringField( "base_ccy"));
			miBuilder.setLocalCurrencyIso(payload.getParsedStringField("local_ccy"));
			miBuilder.setMatchingType(    payload.getParsedIntField(   "matching_type"));
			
			builder.setMarketId(miBuilder.build());
			
			builder.setEventTime(eventTimeMillis);
			
			final String dealId= payload.getParsedStringField("deal_id");
			builder.setDealId(    dealId);
			final long actTime   = payload.getParsedLongField("msg_timestamp_long");
			builder.setDealTime(  actTime);
			builder.setDealAmount(payload.getParsedLongField ("deal_amt_result"));
			builder.setDealRate( payload.getParsedStringField("prc_deal_value"));
			
			// assuming that its DST (and only). will not work properly if it's DSM
			String dstDealSideStr = payload.getParsedStringField("deal_side_value");
			String takerFloorCode = payload.getParsedStringField("taker_floor_code");
			String makerFloorCode = payload.getParsedStringField("maker_floor_code");
			boolean isCentralBankMaker = makerFloorCode.equals(centralBankDealCode);
			boolean isCentralBankBuys  = true; // default value
			
			if ( "buy".equalsIgnoreCase(dstDealSideStr) ) {
				// taker buys
				if ( isCentralBankMaker ) {
					isCentralBankBuys = false;
				} else {
					isCentralBankBuys = true;
				}
			} else {
				// taker sells
				if ( isCentralBankMaker ) {
					isCentralBankBuys = true;
				} else {
					isCentralBankBuys = false;
				}
			}
			
			String counterPartyFloorCode = takerFloorCode;
			if ( !isCentralBankMaker ) {
				counterPartyFloorCode = makerFloorCode;
			}
			builder.setCounterPartyDealCode(counterPartyFloorCode);
			
			if ( isCentralBankBuys ) {
				builder.setDealCodeSide(DealSide.BUY);
			} else {
				builder.setDealCodeSide(DealSide.SELL);
			}
			
			builder.setBaseQuoteId(baseQuoteId);

			String eventId = genEventId(dealId, actTime);
			builder.setEventId(eventId);

			CentralBankDealEvent built = builder.build();
			return built;
		}

		@Override
		protected void doAppendExtraProperties(Message jmsMessage) throws JMSException {
			jmsMessage.setStringProperty(JMS_MSG_PROPERTY_BASE_CURRENCY_ISO
					,payload.getParsedStringField( "base_ccy"));
			jmsMessage.setStringProperty(JMS_MSG_PROPERTY_LOCAL_CURRENCY_ISO
					,payload.getParsedStringField("local_ccy"));
		}
		
	}

}
